ES6之let与const 字符串和正则

let const

let与const其实没什么特别的,主要就是提供的块级作用域,存在于函数内部和块中(字符{和}之间的区域),没有var的变量提升;

1
2
var num = 1 + num1; // throw error
let num1 = 5;

需要注意的点:

禁止重声明

1
2
var msg = 'hi';
let msg = 'test'; // Uncaught SyntaxError: Identifier 'msg' has already been declared

临时死区TDZ(Temporal Dead Zone)

使用let和const定义的变量在定义处到块级作用域开始之间是不能使用定义的变量的,使用会报错;

1
2
3
4
5
var msg = 'hi';
if(true) {
typeof msg; // Uncaught ReferenceError: msg is not defined
let msg = 'hello';
}

循环

由于词法作用域的缘故,执行console.log(i)指向的都市同一个i,最后值是10

1
2
3
4
5
6
7
8
9
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func) {
func(); // outputs the number "10" ten times
});

let声明每次迭代循环会创建一个新变量:

1
2
3
4
5
6
7
8
9
10
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push((function(value) {
return function() {
console.log(value);
} }(i)));
}
funcs.forEach(function(func) {
func(); // outputs 0, then 1, then 2, up to 9
});

全局作用域行为

var在全局作用域定义的是window的一个属性

1
2
3
var msg = "Hello!";
console.log(window.msg); // 'Hello!'
console.log(window.msg === msg); // true

而let和const创建了一个绑定并遮蔽了全局的msg变量;let和const比较安全;

1
2
3
let msg1 = "Hello!";
console.log(window.msg1); // undefined
console.log(window.msg1 === msg1); // false

const绑定的是引用

const绑定是引用,只要引用不变,不报错;

1
2
3
4
const obj = {msg: 1};
obj.msg = 2;
console.log(obj); // {msg: 2}
obj = {msg: 2} // Uncaught TypeError: Assignment to constant variable.

块级绑定最佳实践是尽量用const,只有确实需要改变变量的值时用let。

字符串变动

Unicode支持

ES6出现之前,JS字符一直基于16位字符编码(UTF-16)进行构建。每16位的序列是一个编码单元,代表一个字符。length\chatAt等属性与方法都是基于这种编码单元构成的。

UTF-16编码中:

  • 前2**16个码位均以16位的编码单元表示,这个范围被称作BMP
  • 但是如果超出16位编码范围之外的码位,就无法仅用16位来表示,UTF-16引入了代理对,用两个16位编码单元来表示;

“𠮷”这个字符就是超出16位表示范围的,使用了代理对来表示的,在ES5中:

1
2
3
4
5
6
7
let text = "𠮷";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(text.charAt(0)); // ""
console.log(text.charAt(1)); // ""
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271

codePointAt / fromCodePoint

ES6引入了codePointAt与fromcodePoint来支持超出16位码元,这两个方法接受编码单元的位置而非字符位置作为参数;如:

1
2
3
4
5
6
7
8
let text = "𠮷a";
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
console.log(text.charCodeAt(2)); // 97
console.log(text.codePointAt(0)); // 134071 超出0xffff
console.log(text.codePointAt(1)); // 57271
console.log(text.codePointAt(2)); // 97
console.log(String.fromCodePoint(134071)); // '𠮷'

注意charCodeAt与codePointAt的区别,如上面的text.codePointAt(0)计算了’𠮷’的完整码位-两个编码单元,超出0xffff;

新增字符串方法includes / startWith / endWith / repeat

1
2
3
4
5
6
7
8
9
10
let msg = "Hello world!";
console.log(msg.startsWith("Hello")); // true
console.log(msg.endsWith("!")); // true
console.log(msg.includes("o")); // true
console.log(msg.startsWith("o")); // false
console.log(msg.endsWith("world!")); // true
console.log(msg.includes("x")); // false
console.log(msg.startsWith("o", 4)); // true
console.log(msg.endsWith("o", 8)); // true
console.log(msg.includes("o", 8)); // false

这里需要注意的是endsWith是从用第二个参数减去搜索字符的长度得到的数字位置开始搜索的;如msg.endsWith("o", 8)是从8-len('o')=7开始搜索的;以此类推如果是msg.endsWith("orl", 8)则是从8-len('orl')=5开始搜索返回false;

1
2
3
console.log('hello'.repeat(2)); // hellohello
console.log('a'.repeat(2)); // aa
console.log('bc'.repeat(3)); // bcbcbc

模版字面量

ES6引入了模版字面量语法支持更丰富的字符串功能:

  • 多行字符串
  • 基本的字符串格式化,支持嵌套
  • HTLM转义

看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let message = `hello world!`
console.log(message) // 'hello world'
console.log(typeof message) // 12
// 多行字符串
var msg = `Multiline
string`;
console.log(msg); // 'Multiline
// string'
console.log(message.length); // 16
// 字符串占位
let name = 'world',
msg1 = `Hello, ${name}`;
console.log(message); // "Hello, world!"

除了上面的功能之外,还有一个标签模版的功能;标签可以是一个函数,第一个参数是一个数组,包含JS解释过后的字面量字符串,之后的所有参数都是每一个占位符的解释值;如:

1
2
3
4
5
6
7
8
9
10
11
12
13
function tag(literals, ...substitutions) {
console.log(literals);
console.log(literals.raw)
console.log(substitutions);
return 'hello world!';
}
var name1 = 'lili';
var name2 = 'lucy';
var msg = tag`abc${name1}def${name2}\n`;
// ["abc", "def", "↵", raw: Array(3)]
// ["abc", "def", "\n"]
// ["lili", "lucy"]
console.log(msg); // 'hello world!'

特别要说明的是literals.raw是对应literals的原生字符串信息;如\n\\n;

正则变动

u修饰符

上面我们看到”𠮷”匹配不到/^.$/正则表达式;这是因为正则是通过编码单元来匹配字符串的,而”𠮷”是由两个编码单元组成的,所以/^.$/是匹配不成功的;

为了解决这种问题,ES6引入了一个支持Unicode的u修饰符;当使用了修饰符u,正则表达式就从编码单元模式切换为字符模式,如此一来正则表达式就不会视代理对为两个字符,从而安全按照我们预期正常运行;

1
2
3
4
let text = "𠮷";
console.log(text.length); // 2
console.log(/^.$/.test(text)); // false
console.log(/^.$/u.test(text)); // true

检测是否支持修饰符u:

1
2
3
4
5
6
7
8
function hasRegExpU() {
try {
var pattern = new RegExp(".", "u");
return true;
} catch (ex) {
return false;
}
}

修饰符y

ES6新增了一个正则表达式扩展:修饰符y,主要是影响正则表达式搜索过程中的sticky属性;当在字符串开始匹配时,会通知搜索从正则表达式的lastIndex属性开始进行,如果在指定位置没能匹配成功,则停止继续匹配;看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let text = "hello1 hello2 hello3",
pattern = /hello\d\s?/,
result = pattern.exec(text),
globalPattern = /hello\d\s?/g,
globalResult = globalPattern.exec(text),
stickyPattern = /hello\d\s?/y,
stickyResult = stickyPattern.exec(text);
console.log(result[0]);// "hello1 "
console.log(globalResult[0]);// "hello1 "
console.log(stickyResult[0]);// "hello1 "
pattern.lastIndex = 1;
globalPattern.lastIndex = 1;
stickyPattern.lastIndex = 1;
result = pattern.exec(text);
globalResult = globalPattern.exec(text);
stickyResult = stickyPattern.exec(text);
console.log(result[0]); // "hello1 "
console.log(globalResult[0]);// "hello2 "
console.log(stickyResult[0]);// throws an error!

当指定lastIndex设置为1的时候,stickyPattern没有匹配成功,stickyResult返回null,console.log(stickyResult[0])就报错;

需要注意以下几点:

  1. y和g修饰符如果匹配失败,lastIndex会被重置为0;
  2. y是严格从lastIndex开始匹配,而g从lastIndex匹配失败可以继续往后匹配;
  3. 只有调用exec和test这些正则表达式对象的方法时才会涉及lastIndex属性;调用字符串的方法是不会有效果的;

检测是否支持y修饰符:

1
2
3
4
5
6
7
8
function hasRegExpY() {
try {
var pattern = new RegExp(".", "y");
return true;
} catch (ex) {
return false;
}
}

正则表达式复制

ES5可以通过给RegExp构造函数传递正则表达式作为参数来复制这个表达式, 但是如果第二个参数传递修饰符参数则会报错;

1
2
3
var re1 = /ab/i
var re2 = new RegExp(re1) //
var re3 = new RegExp(re1, 'g') //报错

ES6修改了这个行为,可以传递修饰符var re4 = new RegExp(re1, 'g');但是会把之前的修饰符替换掉,re4变成了/ab/g.

正则表达式flags属性

ES6在正则表达式中新增了flags属性,会输出正则的修饰符;如

1
2
3
let reg = /ab/igu;
console.log(reg.flags); // 'igu'
console.log(reg.source); // 'ab'